﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

// This is a very RAW version. (brainstorm version) (it's worse) but.. will be reworked.

namespace FastBinarySerialization
{
	public delegate void ReadHandler(object obj, BinaryReader reader);
	public delegate void WriteHandler(object obj, BinaryWriter writer);

	public class FastBinarySerializer
	{
		private static Dictionary<Type, List<ReadWriteHandlerInfo>> _registerHandlerDictionary = new Dictionary<Type, List<ReadWriteHandlerInfo>>();
		private static List<ReadWriteHandlerInfo> GetOwnerTypeHandler(Type ownerType)
		{
			List<ReadWriteHandlerInfo> result;

			if (!_registerHandlerDictionary.TryGetValue(ownerType, out result))
			{
				result = new List<ReadWriteHandlerInfo>();
				_registerHandlerDictionary.Add(ownerType, result);
			}
			return result;
		}
		private static Dictionary<Type, Dictionary<string, ReadWriteHandlerInfo>> _typeReadWriteHandlerCache = new Dictionary<Type, Dictionary<string, ReadWriteHandlerInfo>>();
		private static void GetPropertyHandlers(Type type, Dictionary<string, ReadWriteHandlerInfo> list)
		{
			IEnumerable<ReadWriteHandlerInfo> handlers = GetOwnerTypeHandler(type);

			foreach (ReadWriteHandlerInfo handler in handlers)
				list.Add(handler.FullName, handler);

			Type baseType = type.BaseType;
			if (baseType != null)
				GetPropertyHandlers(baseType, list);
		}
		private static Dictionary<string, ReadWriteHandlerInfo> BuildReadWriteHandlers(Type type)
		{
			Dictionary<string, ReadWriteHandlerInfo> handlers = new Dictionary<string, ReadWriteHandlerInfo>();

			GetPropertyHandlers(type, handlers);

			return handlers;
		}

		private static Dictionary<string, ReadWriteHandlerInfo> GetReadWriteHandlers(Type type)
		{
			Dictionary<string, ReadWriteHandlerInfo> result;

			lock (_typeReadWriteHandlerCache)
				if (!_typeReadWriteHandlerCache.TryGetValue(type, out result))
				{
					result = BuildReadWriteHandlers(type);
					_typeReadWriteHandlerCache.Add(type, result);
				}
			return result;
		}


		public static void Register(string name, Type type, Type ownerType, ReadHandler readHandler, WriteHandler writeHandler)
		{
			// register handler on ownertypes
			List<ReadWriteHandlerInfo> ownerTypeHandlers = GetOwnerTypeHandler(ownerType);

			lock (ownerTypeHandlers)
				ownerTypeHandlers.Add(new ReadWriteHandlerInfo()
				{
					Name = name,
					Type = type,
					OwnerType = ownerType,
					ReadHandler = readHandler,
					WriteHandler = writeHandler,
				});
		}
		public static void Serialize(Stream stream, object item)
		{
			Serialize(stream, item, null);
		}
		public static void Serialize(BinaryWriter writer, object item)
		{
			Serialize(writer, item, null);
		}
		public static void Serialize(Stream stream, object item, IEnumerable<string> propertyNames)
		{
			Serialize(new BinaryWriter(stream), item, propertyNames);
		}
		public static void Serialize(BinaryWriter writer, object item, IEnumerable<string> propertyNames)
		{
			Type type = item.GetType();

			HashSet<string> propertyNameHashSet;

			if (propertyNames == null)
				propertyNameHashSet = null;
			else
				propertyNameHashSet = new HashSet<string>(propertyNames);

			Dictionary<string, ReadWriteHandlerInfo> handlers = GetReadWriteHandlers(type);

			// write the object type
			writer.Write(GetTypeFromStr(type));

			ReadWriteHandlerInfo[] handlerArray;

			if (propertyNameHashSet == null)
				handlerArray = handlers.Values.ToArray();
			else
				handlerArray = handlers.Values.Where(handler => propertyNameHashSet.Contains(handler.Name)).ToArray();

			writer.Write(handlerArray.Length);
			foreach (ReadWriteHandlerInfo handler in handlerArray)
			{
				writer.Write(handler.Type.FullName);
				writer.Write(handler.Name);

				using (MemoryStream memoryStream = new MemoryStream())
				{
					BinaryWriter tempWriter = new BinaryWriter(memoryStream);
					handler.WriteHandler(item, tempWriter);

					byte[] data = memoryStream.ToArray();
					writer.Write(data.Length);
					writer.Write(data);
				}
			}
		}

		public static object Deserialize(Stream stream)
		{
			return Deserialize(new BinaryReader(stream));
		}

		private static Dictionary<string, Type> typeToStrCache = new Dictionary<string, Type>();

		private static Type GetTypeFromStr(string typeName)
		{
			Type result;

			lock (typeToStrCache)
				if (!typeToStrCache.TryGetValue(typeName, out result))
				{
					result = Type.GetType(typeName);
					typeToStrCache.Add(typeName, result);
				}
			return result;
		}

		private static Dictionary<Type, string> strToTypeCache = new Dictionary<Type, string>();

		private static string GetTypeFromStr(Type type)
		{
			string result;

			lock (strToTypeCache)
				if (!strToTypeCache.TryGetValue(type, out result))
				{
					result = type.AssemblyQualifiedName;
					strToTypeCache.Add(type, result);
				}
			return result;
		}


		public static object Deserialize(BinaryReader reader)
		{
			string typeName = reader.ReadString();
			Type type = GetTypeFromStr(typeName);

			Dictionary<string, ReadWriteHandlerInfo> handlers = GetReadWriteHandlers(type);
			object obj = Activator.CreateInstance(type);

			int propertyCount = reader.ReadInt32();
			for (int index = 0; index < propertyCount; index++)
			{
				string propertyTypeName = reader.ReadString();
				string propertyName = reader.ReadString();
				string propertyFullname = propertyTypeName + ":" + propertyName;
				int size = reader.ReadInt32();

				ReadWriteHandlerInfo info;

				if (handlers.TryGetValue(propertyFullname, out info))
					info.ReadHandler(obj, reader);
				else
					if (reader.BaseStream.CanSeek)
						reader.BaseStream.Seek(size, SeekOrigin.Current);
					else
						reader.ReadBytes(size);

			}

			return obj;
		}

		public static void DeserializeTo(object obj, Stream stream)
		{
			DeserializeTo(obj, stream, true);
		}
		public static void DeserializeTo(object obj, Stream stream, bool ignoreTypeChecking)
		{
			BinaryReader reader = new BinaryReader(stream);

			string typeName = reader.ReadString();
			Type type = obj.GetType();

			if ((!ignoreTypeChecking) && (typeName != type.FullName))
				throw new InvalidDataException();

			Dictionary<string, ReadWriteHandlerInfo> handlers = GetReadWriteHandlers(type);
			int propertyCount = reader.ReadInt32();
			for (int index = 0; index < propertyCount; index++)
			{
				string propertyTypeName = reader.ReadString();
				string propertyName = reader.ReadString();
				string propertyFullname = propertyTypeName + ":" + propertyName;
				int size = reader.ReadInt32();

				ReadWriteHandlerInfo info;

				if (handlers.TryGetValue(propertyFullname, out info))
					info.ReadHandler(obj, reader);
				else
					if (reader.BaseStream.CanSeek)
						reader.BaseStream.Seek(size, SeekOrigin.Current);
					else
						reader.ReadBytes(size);

			}
		}
	}
}
